toSignal 函數的回傳類型為 Signal<T | undefined>。 Observable 是惰性的,當事件發生時發出第一個值。因此,在 Observable 發出第一個值之前,signal 是 undefined。 如果 toSignal 函數希望 Observable 同步發出,例如 BehaviourSubject 或 startWith,它可以為第二個參數提供 requireSync: true 選項。
在這篇文章中,我將展示 requireSync 選項的兩個用例。
HttpClient 透過 id 查詢一個人,startWith 運算子提供一個初始值。BehaviorSubject 值的按鈕。export type Person = {
name: string;
height: string;
mass: string;
hair_color: string;
skin_color: string;
eye_color: string;
gender: string;
films: string[];
}
import { HttpClient } from "@angular/common/http";
import { inject, Injectable } from "@angular/core";
import { catchError, Observable, of, startWith } from "rxjs";
import { Person } from "./person.type";
const URL = 'https://swapi.dev/api/people';
const DEFAULT: Person = {
name: '',
height: '',
mass: '',
hair_color: '',
skin_color: '',
eye_color: '',
gender: '',
films: [],
};
@Injectable({
providedIn: 'root'
})
export class StarWarService {
private readonly http = inject(HttpClient);
getData(id: number): Observable<Person> {
return this.http.get<Person>(`${URL}/${id}`).pipe(
startWith(DEFAULT)
catchError((err) => {
console.error(err);
return of(DEFAULT);
}));
}
}
StarWarService 建立一個帶有 getData 方法的來呼叫 StarWar API 來檢索人員。 HttpClient 將結果傳送給傳回初始值的 startWith 運算子。 因此,此方法的傳回類型為 Observable<Person>。
import { ChangeDetectionStrategy, Component, inject, Injector, input, OnChanges, Signal } from '@angular/core';
import { StarWarService } from './star-war.service';
import { toSignal } from '@angular/core/rxjs-interop';
import { StarWarPersonComponent } from './star-war-person.component';
import { Person } from './person.type';
@Component({
selector: 'app-star-war',
standalone: true,
imports: [NgStyle, StarWarPersonComponent],
template: `
<h3>Star War Jedi vs Sith</h3>
<app-star-war-person [person]="light()" />
<app-star-war-person [person]="evil()" />
</div>
`,
})
export class StarWarComponent implements OnChanges {
// required signal input
jedi = input.required<number>();
// required signal input
sith = input.required<number>();
starWarService = inject(StarWarService);
injector = inject(Injector);
light!: Signal<Person>;
evil!: Signal<Person>;
ngOnChanges(): void {}
}
在 StarWarComponent 元件中,我注入 StarWarService 和元件的 injector。此外,我聲明了 light 和 evil 的 signals 來儲存從 toSignal 函數傳回的結果。觀察到 signal 刪除了類型中的 undefined。
interface ToSignalOptions<T> {
initialValue?: unknown;
requireSync?: boolean;
injector?: Injector;
manualCleanup?: boolean;
rejectErrors?: boolean;
equal?: ValueEqualityFn<T>;
}
ToSignalOptions 選項有一個 requireSync 屬性,我用它來確保 Observables 在訂閱時發出值。
export class StarWarComponent implements OnChanges {
… same as before …
ngOnChanges(): void {
this.light = toSignal(this.starWarService.getData(this.jedi()), {
injector: this.injector,
requireSync: true,
});
this.evil = toSignal(this.starWarService.getData(this.sith()), {
injector: this.injector,
requireSync: true
});
}
}
在 ngOnChanges 方法中,我呼叫 service 來取得 Observables,並使用 toSignal 函數建立 signal。第二個參數是元件的 injector 和 requireSync 的選項。
<app-star-war-person [person]="light()" kind="Jedi Fighter" />
<app-star-war-person [person]="evil()" kind="Sith Lord" />
接下來,我將 light 和 evil signals 傳遞給 StarWarPersonComponent 元件,以顯示絕地武士和西斯領主的詳細資訊。
import { Route } from '@angular/router';
export const routes: Route[] = [
{
path: 'requireSync-example',
loadComponent: () => import('./require-sync/example.component'),
data: {
btnValues: [-5, -3, 1, 2, 4]
}
},
];
在 routes array中, requireSync-example 路由的路由資料是一個數字數組。
export const appConfig = {
providers: [
provideRouter(routes, withComponentInputBinding()),
]
}
在 appConfig 中,provideRouter 函數的 withComponentInputBinding 功能將路由資料綁定到ExampleComponent元件的 required signal input。
@Component({
selector: 'app-requireSync-example',
standalone: true,
template: `
<div>
@for (v of btnValues(); track v) {
<button (click)="update(v)">{{ v }}</button>
}
</div>
<div>
<p>total: {{ total() }}</p>
<p>source: {{ source.getValue() }}</p>
<p>sum: {{ sum() }}</p>
</div>
<button (click)="changeArray()">Update the BehaviorSubject</button>
`,
})
export default class ExampleComponent {
btnValues = input.required<number[]>();
something = new BehaviorSubject(0);
total = toSignal(this.something, { requireSync: true });
source = new BehaviorSubject([1,2,3,4,5]);
sum = toSignal(
this.source.pipe(map((values) => values.reduce((acc, v) => acc + v, 0))), { requireSync: true });
update(v: number) {
this.something.next(this.something.getValue() + v);
}
changeArray() {
const values = this.source.getValue().length <= 5 ? [11,12,13,14,15,16,17,18] : [1,2,3,4,5];
this.source.next(values);
}
}
Something 是初始值為 0 的 BehaviorSubject,toSignal 函數會從中建立一個 signal。 requireSync 選項是可能的,因為 BehaviorSubject 在被訂閱時可以立即發出一個值。點選時,按鈕會呼叫 update 方法來更新 BehaviorSubject。 HTML 範本在收到新值時顯示 total 的值。
Source 是另一個儲存數字 array 的 BehaviorSubject。 然後,BehaviorSubject 傳送給 map運算子來計算總和。 toSignal 函數和 requireSync: true 斷言事件流 (event stream) 在訂閱時會發出總和。 點選按鈕會執行 changeArray 方法來變更 source BehaviorSubject。由於 total signal 訂閱事件流 ,因此範本呈現來 source 和 total 的新值。
Observable 是 BehaviorSubject 或由諸如 startWith 或 of 之類的的 RxJS 運算子產生值,我們可以將 requireSync 選項傳遞給 toSignal 函數。toSignal 有 requireSync: true 但 Observable 沒有立即發出值,則會拋出錯誤。鐵人賽的第 34 天到此結束。